﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using XmlServer.Listeners;

namespace XmlServer.OperatorManager
{
    public class OperatorInvokers
    {
        SortedList<string, OperatorInvoker> operatorInvokers;

        public OperatorInvokers(Listener listener, string operatorsFolder)
        {
            operatorInvokers = new SortedList<string, OperatorInvoker>();
            LoadOperators(listener, operatorsFolder);
        }

        public int Count
        {
            get { return operatorInvokers.Count; }
        }

        public void Add(OperatorInvoker operatorInvoker)
        {
            try
            {
                Monitor.Enter(this);
                if (operatorInvokers != null && !OperatorInvokerExists(operatorInvoker.FullName))
                {
                    operatorInvokers.Add(operatorInvoker.FullName, operatorInvoker);
                }
            }
            catch { }
            finally
            {
                Monitor.Exit(this);
            }
        }

        public bool OperatorInvokerExists(string FullName)
        {
            if (String.IsNullOrEmpty(FullName)) return false;
            return operatorInvokers.ContainsKey(FullName);
        }

        public void Clear()
        {
            try
            {
                Monitor.Enter(this);
                operatorInvokers.Clear();
            }
            catch { }
            finally
            {
                Monitor.Exit(this);
            }
        }

        public OperatorInvoker GetOperatorInvoker(string FullName)
        {
            OperatorInvoker result = null;
            try
            {
                Monitor.Enter(this);
                if (OperatorInvokerExists(FullName))
                {
                    result = operatorInvokers[FullName];
                }
            }
            catch
            {
                result = null;
            }
            finally
            {
                Monitor.Exit(this);
            }

            return result;
        }

        private void LoadOperators(Listener listener, string operatorsFolder)
        {
            listener.InvokeDebugMessageDelegate("OperatorManager.LoadOperators() is starting");

            DirectoryInfo dir = new DirectoryInfo(operatorsFolder);
            var files = dir.GetFiles("*.dll");

            foreach (var file in files)
            {
                listener.InvokeDebugMessageDelegate(String.Format("OperatorManager.LoadOperators(): Processing operator '{0}'", file.Name));
                try
                {
                    var assembly = this.LoadAssembly(file.FullName);
                    if (assembly == null)
                    {
                        listener.InvokeDebugMessageDelegate(String.Format("OperatorManager.LoadOperators(): The file '{0}' is not an Xml Server Operator; Error Message = Failed to load .Net Assembly", file.FullName));
                        continue;
                    }

                    
                    var iOperatorType = GetIOperatorType(assembly);
                    if (iOperatorType == null)
                    {
                        listener.InvokeDebugMessageDelegate(String.Format("OperatorManager.LoadOperators(): The file '{0}' is not an XML Server Operator; Error Message = Failed to find XmlServer.ServiceContracts.IOperator interface", file.FullName));
                        continue;
                    }


                    var iOperatortObj = GetOperatorObject(iOperatorType);
                    if (iOperatortObj == null)
                    {
                        listener.InvokeDebugMessageDelegate(String.Format("OperatorManager.LoadOperators(): The file '{0}' is not an XML Server Operator; Error Message = Failed to create the XmlServer.ServiceContracts.IOperator interface object", file.FullName));
                        continue;
                    }

                    // create the operator invoker
                    OperatorInvoker opInvoker = new OperatorInvoker();
                    opInvoker.Filename = file.FullName;
                    opInvoker.OperatorType = iOperatorType;
                    opInvoker.OperatorObject = iOperatortObj;

                    opInvoker.Name = GetProperty(iOperatorType, iOperatortObj, "Name");
                    if (String.IsNullOrEmpty(opInvoker.Name))
                    {
                        listener.InvokeDebugMessageDelegate(String.Format("OperatorManager.LoadOperators(): The file '{0}' is not an XML Server Operator; Error Message = Failed to get operator Name property", file.FullName));
                        continue;
                    }

                    opInvoker.Version = GetProperty(iOperatorType, iOperatortObj, "Version");
                    if (String.IsNullOrEmpty(opInvoker.Name))
                    {
                        listener.InvokeDebugMessageDelegate(String.Format("OperatorManager.LoadOperators(): The file '{0}' is not an XML Server Operator; Error Message = Failed to get operator Version property", file.FullName));
                        continue;
                    }

                    if (OperatorInvokerExists(opInvoker.FullName))
                    {
                        listener.InvokeDebugMessageDelegate(String.Format("OperatorManager.LoadOperators(): The operator already exists in the operators collection.  Filename = {0}, Name = {1}, Version = {2}", file.FullName, opInvoker.Name, opInvoker.Version));
                        continue;
                    }

                    this.Add(opInvoker);
                    listener.InvokeDebugMessageDelegate(String.Format("OperatorManager.LoadOperators(): Operator '{0}' successfully processed", opInvoker.FullName));

                }
                catch (Exception err)
                {
                    listener.InvokeDebugMessageDelegate(String.Format("OperatorManager.LoadOperators(): The file '{0}' is not an Operator; Error Message = {1}", file.FullName, err.ToString()));
                }
            }

            listener.InvokeDebugMessageDelegate("OperatorManager.LoadOperators() is finished");
        }


        private System.Reflection.Assembly LoadAssembly(string Filename)
        {
            return Assembly.LoadFrom(Filename);
        }

        private System.Type GetIOperatorType(Assembly operatorAssembly)
        {
            System.Type iOperatorType = null;

            var types = operatorAssembly.GetTypes();
            bool foundIOperator = false;

            foreach (System.Type _type in types)
            {
                System.Type interfaceType = null;
                try
                {
                    interfaceType = _type.GetInterface("XmlServer.ServiceContracts.IOperator");
                }
                catch
                {
                    interfaceType = null;
                }


                if (interfaceType != null)
                {
                    foundIOperator = true;
                    iOperatorType = _type;
                    break;
                }

            }

            if (!foundIOperator) return null;

            return iOperatorType;
           
        }

        private object GetOperatorObject(System.Type iOperatorType)
        {
            return Activator.CreateInstance(iOperatorType);
        }

        private string GetProperty(System.Type iOperatorType, Object iOperatortObj, string PropertyName)
        {
            PropertyInfo property = iOperatorType.GetProperty(PropertyName);
            if (property == null) return string.Empty;
            return (String)property.GetValue(iOperatortObj, null);
        }

        public IEnumerator<KeyValuePair<string, OperatorInvoker>> GetEnumerator()
        {
            return operatorInvokers.GetEnumerator();
        }

        public IList<string> OperatorNames()
        {
            return this.operatorInvokers.Keys;
        }

    }
}
